﻿#include "../../../includes/multimedia/rtsp/crtsppusher.h"
#include "../../../includes/QsLog/QsLog.h"
#include "../../../includes/common/common.h"
#include "../../../includes/common/NedAllocatorImpl.h"

#include <ctime>
#include <random>
#include <QImage>

CRtspPusher::CRtspPusher(QObject *parent,NetworkFrameManager *pNetworkFrameManager)
    : QTcpSocket(parent),
      m_NetworkFrameManager(pNetworkFrameManager),
      m_Method(OPTIONS),
      m_isCloseWindows(false),
      m_Cseq(0),
      m_is_setup(false),
      m_is_play(false),
      m_has_key_frame(false),
      m_clock_rate(0)
{
    connect(this, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
    connect(this, SIGNAL(connected()), this, SLOT(onConnected()));
    connect(this, SIGNAL(disconnected()), this, SLOT(onDisconnected()));

    m_H264Source.setRtspPusher(this);

    std::random_device rd;
    memset(&m_RtpHeader,0,sizeof(m_RtpHeader));
    m_RtpHeader.version = RTP_VERSION;
    m_RtpHeader.seq = 0;
    m_RtpHeader.ts = htonl(rd());
    m_RtpHeader.ssrc = htonl(rd());
    m_packet_seq = rd()%0xffff;
}

CRtspPusher::~CRtspPusher()
{
    m_isCloseWindows=true;
    this->Close();
    this->clearnH264();
}

/**
 * @brief CRtspPusher::openUrl 连接rtsp流媒体服务器
 * @param url rtsp流媒体服务器地址
 * @return 如果服务器连接成功返回真，否则返回假
 */
bool CRtspPusher::openUrl(QString url)
{
    if(url == "") return false;
    
    QStringList tmpParams1 = url.split("//");
    if(tmpParams1.size() < 2)
    {
        Log(QString::fromLocal8Bit("参数个数不对."),"error");
        return false;
    }

    QStringList tmpParams2 = tmpParams1[1].split("/");
    if(tmpParams2.size() < 2)
    {
        Log(QString::fromLocal8Bit("参数个数不对."),"error");
        return false;
    }

    QStringList tmpParams3 = tmpParams2[0].split(":");
    if(tmpParams3.size() < 2)
    {
        Log(QString::fromLocal8Bit("参数个数不对."),"error");
        return false;
    }

    if(tmpParams1[0].toLower() != "rtsp:")
    {
        Log(QString::fromLocal8Bit("参数格式不对:")+tmpParams1[0],"error");
        return false;
    }

    if(this->IsConnected())
        this->Close();

    m_RtspUrlInfo.url = url;
    m_RtspUrlInfo.ip = tmpParams3[0];
    m_RtspUrlInfo.port = tmpParams3[1].toInt();
    m_RtspUrlInfo.suffix = tmpParams2[1];

    Log(QString::fromLocal8Bit("正在连接rtsp服务器:")+
        url);

    this->connectToHost(QHostAddress(m_RtspUrlInfo.ip),
                        m_RtspUrlInfo.port);

    return true;
}

/**
 * @brief CRtspPusher::Close 断开服务器连接
 */
void CRtspPusher::Close()
{
    this->disconnectFromHost();

    m_isCloseWindows=false;
    m_Method=MNONE;
    m_Cseq=0;
    m_is_setup=false;
    m_is_play=false;
}

/**
 * @brief CRtspPusher::Log 记录日志信息
 * @param msg 要记录的日志信息
 * @param type 要记录的日志的类型
 */
void CRtspPusher::Log(QString msg, QString type)
{
    if(msg.isEmpty() || type.isEmpty())
        return;

    qDebug()<<msg;

    if(type == "info")
        QLOG_INFO()<<msg;
    else if(type == "warn")
        QLOG_WARN()<<msg;
    else if(type == "debug")
        QLOG_DEBUG()<<msg;
    else if(type == "error")
        QLOG_ERROR()<<msg;
    else if(type == "fatal")
        QLOG_FATAL()<<msg;

    emit signalLog(msg,type);
}

/**
 * @brief CRtspServer::initH264 初始化h264
 */
void CRtspPusher::initH264(QSize screenSize)
{
    m_contextSize = screenSize;
    m_iNal = 0;
    m_Nals = NULL;
    m_Handle = NULL;
    m_Pic_in = (x264_picture_t*)allocBytes(sizeof(x264_picture_t));
    m_Pic_out = (x264_picture_t*)allocBytes(sizeof(x264_picture_t));
    m_Param = (x264_param_t*)allocBytes(sizeof(x264_param_t));

    x264_param_default(m_Param);   //给参数结构体赋默认值
    x264_param_default_preset(m_Param, "fast" , "zerolatency" );  //设置preset和tune

    //修改部分参数
    m_Param->i_csp=X264_CSP_I420;
    m_Param->i_width   = screenSize.width();   // 宽度
    m_Param->i_height  = screenSize.height();  // 高度
    m_Param->i_fps_num  = 25;     // 设置帧率（分子）
    m_Param->i_fps_den  = 1;      // 设置帧率时间1s（分母）

    m_Param->i_threads  = X264_SYNC_LOOKAHEAD_AUTO;
    m_Param->i_keyint_max = 10;              //在此间隔设置IDR关键帧

    m_Param->rc.i_bitrate = 1200;       // 设置码率,在ABR(平均码率)模式下才生效，且必须在设置ABR前先设置bitrate
    m_Param->rc.i_rc_method = X264_RC_ABR;  // 码率控制方法，CQP(恒定质量)，CRF(恒定码率,缺省值23)，ABR(平均码率)

    //set profile
    x264_param_apply_profile(m_Param, "baseline");

    //open encoder
    m_Handle = x264_encoder_open(m_Param);

    x264_picture_init(m_Pic_out);
    x264_picture_alloc(m_Pic_in, X264_CSP_I420, m_Param->i_width, m_Param->i_height);
    m_Pic_in->i_pts=0;

    m_baseFrameSize=screenSize.width()*screenSize.height();
    mimageBuffer=(quint8*)allocBytes(screenSize.width()*screenSize.height()*3);

    m_SwsContext= sws_getContext(screenSize.width(), screenSize.height(),
                                 m_srcFormat,
                                 screenSize.width(), screenSize.height(),
                                 AV_PIX_FMT_YUV420P,
                                 SWS_BILINEAR, NULL, NULL, NULL);

    int ret;
    if ((ret = av_image_alloc(src_data, src_linesize,
            screenSize.width(), screenSize.height(), m_srcFormat, 8)) < 0)
    {
        qDebug()<<"av_image_alloc failed.";
    }
}

/**
 * @brief CRtspServer::clearnH264 卸载h264
 */
void CRtspPusher::clearnH264(void)
{
    if(m_Pic_in != NULL)
        x264_picture_clean(m_Pic_in);
    if(m_Handle != NULL)
        x264_encoder_close(m_Handle);
    m_Handle = NULL;

    if(m_Pic_in != NULL)
        deallocBytes(m_Pic_in);
    if(m_Pic_out != NULL)
        deallocBytes(m_Pic_out);
    if(m_Param != NULL)
        deallocBytes(m_Param);

    if(m_SwsContext)
    {
        av_freep(&src_data[0]);
        sws_freeContext(m_SwsContext);
        m_SwsContext = NULL;
    }

    if(mimageBuffer)
        deallocBytes(mimageBuffer);
    mimageBuffer=NULL;
}

/**
 * @brief CRtspPusher::IsConnected 检测和服务器是否连接成功
 * @return
 */
bool CRtspPusher::IsConnected()
{
    return (this->state() == QAbstractSocket::ConnectedState ? true : false);
}

/**
 * @brief CRtspPusher::PushImage 推送图片数据
 * @param image
 * @return
 */
bool CRtspPusher::PushImage(QImage& image)
{
    if(mimageBuffer == NULL || image.isNull())
        return false;

    memcpy(mimageBuffer,image.scaled(m_contextSize.width(), m_contextSize.height(),
                 Qt::IgnoreAspectRatio, Qt::SmoothTransformation)
            .convertToFormat(QImage::Format_RGB888).constBits(),
           m_contextSize.width()*m_contextSize.height()*3);

    return HandleYuv240PFrame(mimageBuffer);
}

/**
 * @brief CRtspServer::HandleYuv240PFrame 处理YUV240P数据
 * @param yuvData
 * @return
 */
bool CRtspPusher::HandleYuv240PFrame(unsigned char* yuvData)
{
    if(m_Handle == NULL || yuvData == NULL)
        return false;

    memcpy(src_data[0],yuvData,m_baseFrameSize*3);

    sws_scale(m_SwsContext,
              src_data,src_linesize,
              0,m_contextSize.height(),
              m_Pic_in->img.plane,
              m_Pic_in->img.i_stride);

    int ret = x264_encoder_encode(m_Handle, &m_Nals, &m_iNal,m_Pic_in, m_Pic_out);
    if (ret< 0)
        return false;

    for (int j = 0; j < m_iNal; ++j)
    {
        AVFrame videoFrame = {0};
        videoFrame.type = 0;
        videoFrame.size = m_Nals[j].i_payload;
        videoFrame.timestamp = H264Source::GetTimestamp();
        videoFrame.buffer.reset(new uint8_t[videoFrame.size]);
        memcpy(videoFrame.buffer.get(), m_Nals[j].p_payload, videoFrame.size);

        PushFrame(&videoFrame);
    }

    return true;
}

/**
 * @brief CRtspPusher::PushFrame 推送帧数据
 * @param frame
 * @return
 */
bool CRtspPusher::PushFrame(AVFrame *frame)
{
    if(!m_is_play)
        return false;

    return m_H264Source.HandleFrame(frame);
}

void CRtspPusher::SetFrameType(uint8_t frame_type)
{
    if(!m_has_key_frame && (frame_type == 0 || frame_type == VIDEO_FRAME_I)) {
        m_has_key_frame = true;
    }
}

void CRtspPusher::SetRtpHeader(RtpPacket *pkt)
{
    if(pkt == NULL) return;

    if(m_is_play && m_has_key_frame)
    {
        m_RtpHeader.marker = pkt->last;
        m_RtpHeader.ts = htonl(pkt->timestamp);
        m_RtpHeader.seq = htons(m_packet_seq++);
        memcpy(pkt->data.get()+4, &m_RtpHeader, RTP_HEADER_SIZE);
    }
}

/**
 * @brief CRtspPusher::sendUdpData 发送Packet数据
 * @return
 */
qint64 CRtspPusher::sendPacketData(RtpPacket *pkt)
{
    if(pkt == NULL)
        return 0;

    this->SetFrameType(pkt->type);
    this->SetRtpHeader(pkt);

    qint64 returnsize = 0;
    if(m_is_play && m_has_key_frame)
    {
        uint8_t* rtpPktPtr = pkt->data.get();
        rtpPktPtr[0] = '$';
        rtpPktPtr[1] = (char)0;
        rtpPktPtr[2] = (char)(((pkt->size-4)&0xFF00)>>8);
        rtpPktPtr[3] = (char)((pkt->size -4)&0xFF);

        //char str[128];
        //sprintf(str,"%x %x %x %x\n",(pkt->size -4),rtpPktPtr[2],rtpPktPtr[3],(rtpPktPtr[2]<<8)|rtpPktPtr[3]);
        //qDebug()<<str;

        returnsize = send2((const char*)rtpPktPtr,pkt->size);

        if(returnsize < 0 || returnsize != pkt->size)
        {
            Log(QString::fromLocal8Bit("数据长度不对，数据发送失败."));
            this->Close();
        }
    }

    return returnsize;
}

int CRtspPusher::BuildAnnounceReq(const char* buf, int buf_size, const char *sdp)
{
    memset((void*)buf, 0, buf_size);
    snprintf((char*)buf, buf_size,
            "ANNOUNCE %s RTSP/1.0\r\n"
            "Content-Type: application/sdp\r\n"
            "CSeq: %u\r\n"
            "User-Agent: %s\r\n"
            "Session: %s\r\n"
            "Content-Length: %d\r\n"
            "\r\n"
            "%s",
            m_RtspUrlInfo.url.toStdString().c_str(),
            ++m_Cseq,
            USER_AGENT,
            "",
            (int)strlen(sdp),
            sdp);

    m_Method = ANNOUNCE;
    return (int)strlen(buf);
}

int CRtspPusher::BuildOptionReq(const char* buf, int buf_size)
{
    memset((void*)buf, 0, buf_size);
    snprintf((char*)buf, buf_size,
            "OPTIONS %s RTSP/1.0\r\n"
            "CSeq: %u\r\n"
            "User-Agent: %s\r\n"
            "\r\n",
            m_RtspUrlInfo.url.toStdString().c_str(),
            ++m_Cseq,
            USER_AGENT);

    return (int)strlen(buf);
}

int CRtspPusher::BuildRecordReq(const char* buf, int buf_size)
{
    memset((void*)buf, 0, buf_size);
    snprintf((char*)buf, buf_size,
            "RECORD %s RTSP/1.0\r\n"
            "Range: npt=0.000-\r\n"
            "CSeq: %u\r\n"
            "User-Agent: %s\r\n"
            "Session: %s\r\n"
            "\r\n",
            m_RtspUrlInfo.url.toStdString().c_str(),
            ++m_Cseq,
            USER_AGENT,
            m_session.c_str());

    m_Method = RECORD;
    //m_is_play = true;
    return (int)strlen(buf);
}

int CRtspPusher::BuildSetupTcpReq(const char* buf, int buf_size, int trackId)
{
    int interleaved[2] = { 0, 1 };
    if (trackId == 1) {
        interleaved[0] = 2;
        interleaved[1] = 3;
    }

    memset((void*)buf, 0, buf_size);
    snprintf((char*)buf, buf_size,
            "SETUP %s/track%d RTSP/1.0\r\n"
            "Transport: RTP/AVP/TCP;unicast;mode=record;interleaved=%d-%d\r\n"
            "CSeq: %u\r\n"
            "User-Agent: %s\r\n"
            "Session: %s\r\n"
            "\r\n",
            m_RtspUrlInfo.url.toStdString().c_str(),
            trackId,
            interleaved[0],
            interleaved[1],
            ++m_Cseq,
            USER_AGENT,
            "");

    m_Method = SETUP;
    m_is_setup = true;
    return (int)strlen(buf);
}

/**
 * @brief CRtspPusher::SendRecord 发送RECORD
 */
void CRtspPusher::SendRecord()
{
    std::shared_ptr<char> req(new char[4096], std::default_delete<char[]>());
    int size = this->BuildRecordReq(req.get(), 4096);
    this->send2(req.get(), size);
}

/**
 * @brief CRtspPusher::SendSetup 发送SETUP
 */
void CRtspPusher::SendSetup()
{
    std::shared_ptr<char> req(new char[4096], std::default_delete<char[]>());
    int size = this->BuildSetupTcpReq(req.get(), 4096, 0);
    this->send2(req.get(), size);
}

/**
 * @brief CRtspPusher::SendAnnounce 发送ANNOUNCE
 */
void CRtspPusher::SendAnnounce()
{
    std::string sdp = this->GetSdpMessage();
    if (sdp == "")
    {
        Log(QString::fromLocal8Bit("SendAnnounce发送失败,sdp为空."));
        this->Close();
        return;
    }

    this->SetClockRate(m_H264Source.GetClockRate());
    this->SetPayloadType(m_H264Source.GetPayloadType());

    std::shared_ptr<char> req(new char[4096], std::default_delete<char[]>());
    int size = this->BuildAnnounceReq(req.get(), 4096, sdp.c_str());
    this->send2(req.get(), size);
}

/**
 * @brief CRtspPusher::SendOptions 发送OPTIONS
 */
void CRtspPusher::SendOptions(void)
{
    std::shared_ptr<char> req(new char[2048], std::default_delete<char[]>());
    int size = this->BuildOptionReq(req.get(), 2048);
    this->send2(req.get(), size);
}

/**
 * @brief CRtspPusher::send2
 * @param data
 * @param size
 * @return
 */
qint64 CRtspPusher::send2(const char *data,qint32 size)
{
    return send(QByteArray(data,size));
}

/**
 * @brief CRtspPusher::send 封包并发送
 * @param data 要发送的数据
 *
 * @return 返回发送成功的数据
 */
qint64 CRtspPusher::send(QByteArray data)
{
    if(this->state() !=  QTcpSocket::ConnectedState ||
       data.isEmpty())
    {
        return -1;
    }

    QByteArray sendData = data;
    qint64 m_sendsize,m_totalsize;
    int m_sendcount = 0;

    m_totalsize = sendData.size();
    m_sendsize=0;

    do
    {
        QByteArray psendbytearray;

        if(sendData.size()-m_sendsize < BUF_SIZE)
            psendbytearray = sendData.mid(m_sendsize);
        else
            psendbytearray = sendData.mid(m_sendsize,BUF_SIZE);

        m_sendsize += this->write(psendbytearray);

        if(m_sendsize <= 0) m_sendcount+=1;

        // 如果5次数据都没发送成功，就跳出
        if(m_sendcount > 5)
            break;
    }
    while(m_sendsize < m_totalsize);

    return m_sendsize;
}

bool CRtspPusher::ParseResponse(QByteArray& data)
{
    if(data.isEmpty())
        return false;

    if(data.indexOf("\r\n\r\n") > 0)
    {
        if(data.indexOf("OK") < 0)
            return false;

        int tmpPos = data.indexOf("Session");
        if(tmpPos > 0)
        {
            QString tmpString = data.mid(tmpPos);
            char session_id[50] = {0};
            if (sscanf(tmpString.toStdString().c_str(), "%*[^:]: %s", session_id) == 1)
                m_session = session_id;
        }
    }

    return true;
}

std::string CRtspPusher::GetSdpMessage(void)
{
    char buf[2048] = {0};

    snprintf(buf, sizeof(buf),
            "v=0\r\n"
            "o=- 9%ld 1 IN IP4 %s\r\n"
            "t=0 0\r\n"
            "a=control:*\r\n" ,
            (long)std::time(NULL), m_RtspUrlInfo.ip.toStdString().c_str());

    snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
            "%s\r\n",
            m_H264Source.GetMediaDescription(0).c_str());

    snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
            "%s\r\n",
            m_H264Source.GetAttribute());

    snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
            "a=control:track%d\r\n", 0);

    return buf;
}

void CRtspPusher::HandleRecord()
{
    if(m_is_setup)
    {
        m_is_play = true;

        Log(QString::fromLocal8Bit("系统准备完成,可以发送数据."));

        emit signalReadytofinish();
    }
}

/**
 * @brief CRtspPusher::HandleRtspRequest
 * @param data
 * @return
 */
bool CRtspPusher::HandleRtspRequest(QByteArray& data)
{
    if(!this->ParseResponse(data))
        return false;

    switch(m_Method)
    {
    case OPTIONS:
        SendAnnounce();
        break;
    case ANNOUNCE:
        SendSetup();
        break;
    case SETUP:
        SendRecord();
        break;
    case RECORD:
        HandleRecord();
        break;
    default:
        break;
    }

    return true;
}

/**
 * @brief CRtspPusher::onReadyRead 处理接收数据
 */
void CRtspPusher::onReadyRead()
{
    QByteArray reciverData = this->readAll();

    if(reciverData.isEmpty() || !HandleRtspRequest(reciverData))
        return;

    if(m_NetworkFrameManager) m_NetworkFrameManager->OnProcessNetBinary(this,reciverData);

    emit signalsNetBinary(reciverData);
}

/**
 * @brief CRtspPusher::onDisconnected 处理连接关闭
 */
void CRtspPusher::onDisconnected()
{
    if(m_isCloseWindows) return;

    Log(QString::fromLocal8Bit("与服务器断开连接."));

    if(m_NetworkFrameManager) m_NetworkFrameManager->OnProcessDisconnectedNetMes(this);

    emit sockDisConnect();
}

/**
 * @brief CRtspPusher::onConnected 处理连接成功
 */
void CRtspPusher::onConnected()
{
    Log(QString::fromLocal8Bit("与服务器连接成功."));

    if(m_NetworkFrameManager) m_NetworkFrameManager->OnProcessConnectedNetMes(this);

    emit sockConnected();

    // 发送OPTIONS
    SendOptions();
}
